import wx
import extern.customtreectrl as CT
import extern.treemixin as treemix

from widgets import TransientPopup

from constants import _globalIcons, _knownPackages, object_types, _treeActions
from constants import _leftIcons, _defaultIgnoreList
from constants import ONE_ONLY, TWO_ONLY


class DBPanelTree(wx.Panel):

    def __init__(self, parent):

        wx.Panel.__init__(self, parent)
        
        self.dbTree = DBTree(self)
        
        self.static = wx.StaticText(self, -1, u"Filter tree:")
        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
        font.SetWeight(wx.BOLD)
        self.static.SetFont(font)

        self.filterCtrl = wx.SearchCtrl(self, style=wx.TE_PROCESS_ENTER)
        self.filterCtrl.ShowCancelButton(True)
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.dbTree, 1, wx.EXPAND)        
        mainSizer.Add(self.static, 0, wx.TOP|wx.LEFT, 5)
        mainSizer.Add(self.filterCtrl, 0, wx.EXPAND|wx.ALL, 5)
        
        if 'wxMac' in wx.PlatformInfo:
            mainSizer.Add((5, 5))  # Make sure there is room for the focus ring

        self.SetSizer(mainSizer)

        mainSizer.Layout()
        
        self.filterCtrl.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnSearchCancelBtn)
        self.filterCtrl.Bind(wx.EVT_TEXT_ENTER, self.OnSearch)

        self.static.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
        self.filterCtrl.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)


    def SetFilter(self, filter):

        self.filterCtrl.ChangeValue(filter)        


    def OnSearchCancelBtn(self, event):

        event.Skip()
        self.dbTree.SetFilter(u"")        


    def OnSearch(self, event):

        event.Skip()
        value = self.filterCtrl.GetValue()
        self.dbTree.SetFilter(value.strip())


    def OnUpdateUI(self, event):

        obj = event.GetEventObject()
        mode = self.dbTree.mode
        
        obj.Enable(mode is not None and mode.package_class is not None)

        
class DBTree(CT.CustomTreeCtrl, treemix.ExpansionState):

    def __init__(self, parent, style=wx.SUNKEN_BORDER,
                 agwStyle=CT.TR_HAS_BUTTONS|CT.TR_FULL_ROW_HIGHLIGHT|
                 CT.TR_HAS_VARIABLE_ROW_HEIGHT|CT.TR_HIDE_ROOT):

        CT.CustomTreeCtrl.__init__(self, parent, style=style, agwStyle=agwStyle)
        
        self.MainFrame = wx.GetTopLevelParent(self)
        self.SetBackgroundColour(wx.WHITE)

        self.selecteditem = None

        self.tipItem = None
        self.tipWindow = None
        self.mode = None
        
        self.BuildImageList()
        self.BuildLeftImageList()
        
        self.BindEvents()

        fontItalic = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        fontNormal = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        boldFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        boldItalicFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        bold10Font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        smallerFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        smallerItalicFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)

        pointSize = fontNormal.GetPointSize()
        fontNormal.SetPointSize(pointSize+1)
        
        fontItalic.SetStyle(wx.ITALIC)
        fontItalic.SetPointSize(pointSize+1)
        boldFont.SetWeight(wx.BOLD)
        boldFont.SetPointSize(pointSize+1)
        boldItalicFont.SetWeight(wx.BOLD)
        boldItalicFont.SetPointSize(pointSize+1)
        boldItalicFont.SetStyle(wx.ITALIC)
        bold10Font.SetWeight(wx.BOLD)
        bold10Font.SetPointSize(pointSize+3)
        smallerFont.SetPointSize(pointSize)
        smallerItalicFont.SetPointSize(pointSize)
        smallerItalicFont.SetStyle(wx.ITALIC)

        self.itemfont = fontNormal
        self.italicfont = fontItalic
        self.boldFont = boldFont
        self.boldItalicFont = boldItalicFont
        self.bold10Font = bold10Font
        self.smallerFont = smallerFont
        self.smallerItalicFont = smallerItalicFont

        self.grayColour = wx.Colour(150, 150, 150)        

        self.SetFont(fontNormal)        
        
        self.EnableSelectionVista(True)
        self.SetSpacing(10)


    def BuildImageList(self):
        
        imglist = wx.ImageList(16, 16)
        for bmp in _globalIcons:
            bmp = self.MainFrame.CreateBitmap(bmp)
            imglist.Add(bmp)

        imglist.Add(self.MainFrame.CreateBitmap("warning"))
        self.AssignImageList(imglist)


    def BuildLeftImageList(self):        

        imglist = wx.ImageList(16, 16)
        for bmp in _leftIcons:
            bmp = self.MainFrame.CreateBitmap(bmp)
            imglist.Add(bmp)

        self.AssignLeftImageList(imglist)


    def ReformatImageList(self):

        imglist = self.GetImageList()
        library = self.mode.package_class.GetName()
        library = library.split("-")[0].lower()

        for pkgs in _knownPackages:
            if pkgs in library:
                bmp = self.MainFrame.CreateBitmap(pkgs)
                imglist.Replace(0, bmp)
                return

        bmp = self.MainFrame.CreateBitmap("python")
        imglist.Replace(0, bmp)


    def PopulateTree(self, mode):

        self.populating = True

        self.mode = mode

        package_class = mode.package_class
        other_package_class = mode.other_package_class
        filter = mode.filter
        expansionState = mode.expansionState
        self.selection = mode.selectedItem

        self.ReformatImageList()

        self.Freeze()
        wx.BeginBusyCursor()
        
        self.DeleteAllItems()
        self.MakeRoot()

        self.status = False
        
        if other_package_class:
            self.Compare()
        else:
            if filter:
                self.Walk()
            else:
                if any(expansionState):
                    self.SetExpansionState(expansionState)
                else:
                    self.Expand(self.root)
            
        self.GetParent().SetFilter(filter)

        if not self.selection:
            self.ScrollTo(self.root)
        else:
            self.MakeCurrent(self.selecteditem)
            
        self.populating = False
        self.Thaw()
        wx.CallAfter(wx.EndBusyCursor)


    def MakeCurrent(self, selecteditem):

        self.Unselect()

        if selecteditem:
            self.selecteditem = selecteditem
            self._current = self._key_current = self.selecteditem
            self.selecteditem.SetHilight(True)
            self.ScrollTo(self.selecteditem)
            self.RefreshLine(self.selecteditem)
        

    def MakeRoot(self):

        package_class = self.mode.package_class
        rootName, rootImage = package_class.GetName(), package_class.GetImage()

        dummy_root = self.AddRoot('')

        if self.mode.ignoreList != _defaultIgnoreList:
            warningName = "Ignore list is active!"
            image = self.GetImageList().GetImageCount() - 1
            warningItem = self.AppendItem(dummy_root, warningName, image=image, data=None)
            self.SetItemFont(warningItem, self.boldItalicFont)
            self.SetItemTextColour(warningItem, wx.RED)
        
        self.root = self.AppendItem(dummy_root, rootName, image=rootImage, data=package_class)
        self.SetItemFont(self.root, self.bold10Font)
        self.SetItemHasChildren(self.root, True)

        self.selecteditem = None
        self.status = False
        

    def RecurseChildren(self, item, klass):

        showInherited = self.MainFrame.showInherited
        
        for obj in klass.GetChildren():
                
            if not showInherited and obj.is_redundant:
                continue
            
            img, name = obj.GetImage(), obj.GetShortName()
            subitem = self.AppendItem(item, name, image=img, data=obj)

            self.FormatSubItem(obj, subitem)

        self.FormatSubItem(klass, item) 


    def FormatSubItem(self, obj, subitem, diffs=None):

        children = obj.GetChildren()
        boldFont = self.boldFont
        grey = self.MainFrame.greyInherited

        if children and diffs is None:
            self.SetItemHasChildren(subitem, True)
            
        if obj.GetImage() <= object_types.PYW_MODULE:
            self.SetItemFont(subitem, ((obj.is_redundant and grey) and [self.boldItalicFont] or [self.boldFont])[0])
        elif obj.GetImage() > object_types.KLASS:
            self.SetItemFont(subitem, ((obj.is_redundant and grey) and [self.smallerItalicFont] or [self.smallerFont])[0])
        else:
            self.SetItemFont(subitem, ((obj.is_redundant and grey) and [self.italicfont] or [self.itemfont])[0])

        if diffs >= 0 and diffs != '':
            action, colour, description = _treeActions[diffs]
            eval("self.%s"%(action%colour))
            
        if obj.is_redundant and grey:
            self.SetItemTextColour(subitem, self.grayColour)

        if self.MainFrame.highlightDocs and not obj.docs:
            self.SetItemLeftImage(subitem, 0)

        if diffs == ONE_ONLY:
            self.SetItemLeftImage(subitem, 1)
        elif diffs == TWO_ONLY:
            self.SetItemLeftImage(subitem, 2)
            
        if self.selection:
            if subitem.GetText() == self.selection:
                self.selecteditem = subitem

        subitem.diffs = diffs
        

    def Walk(self):

        package_class = self.mode.package_class
        filter = self.mode.filter
        
        items = package_class.Walk(package_class, filter)
        start = self.root
        showInherited = self.MainFrame.showInherited
        
        appended = {package_class.name: self.root}
        
        for item in items:

            hierarchy = [item]
            parent = item.parent

            while parent:
                hierarchy.append(parent)
                parent = parent.parent

            hierarchy = hierarchy[0:-1]
            hierarchy.reverse()

            stop = False
            for h in hierarchy:
                if not showInherited and h.is_redundant:
                    stop = True

            if stop:
                continue

            start = self.root
            
            while hierarchy:
                obj = hierarchy[0]

                if obj.parent.name in appended:
                    start = appended[obj.parent.name]

                if obj.name not in appended:

                    if showInherited or not obj.is_redundant:
                        subitem = self.AppendItem(start, obj.GetShortName(), image=obj.GetImage(), data=obj)
                        self.FormatSubItem(obj, subitem)
                        appended[obj.name] = subitem
                        start = subitem

                hierarchy.pop(0)

        del appended

        self.ExpandAll()


    def Compare(self):

        package_class, other_package_class = self.mode.package_class, self.mode.other_package_class
        ignoreList = self.mode.ignoreList
        
        items = package_class.Compare(package_class, other_package_class, ignoreList)
        start = self.root
        showInherited = self.MainFrame.showInherited
        filter = self.mode.filter
        
        appended = {package_class.name: self.root}
        
        for item1, item2, differences in items:

            if filter not in item1.GetShortName().lower():
                continue

            hierarchy1, hierarchy2 = [item1], [item2]
            parent1, parent2 = item1.parent, item2.parent

            while parent1:
                hierarchy1.append(parent1)
                parent1 = parent1.parent

            while parent2:
                hierarchy2.append(parent2)
                parent2 = parent2.parent

            hierarchy1 = hierarchy1[0:-1]
            hierarchy1.reverse()

            hierarchy2 = hierarchy2[0:-1]
            hierarchy2.reverse()

            stop = False
            for h1, h2 in zip(hierarchy1, hierarchy2):
                if not showInherited and (h1.is_redundant or h2.is_redundant):
                    stop = True

            if stop:
                continue

            start = self.root
            
            while hierarchy1:
                obj1, obj2 = hierarchy1[0], hierarchy2[0]

                if obj1.parent.name in appended:
                    start = appended[obj1.parent.name]

                if obj1.name not in appended:
                    if obj1 == item1 or obj2 == item1:
                        diffs = differences
                    else:
                        diffs = ''

                    subitem = self.AppendItem(start, obj1.GetShortName(), image=obj1.GetImage(), data=[obj1, obj2])
                        
                    self.FormatSubItem(obj1, subitem, diffs=diffs)
                    appended[obj1.name] = subitem
                    start = subitem

                hierarchy1.pop(0)
                hierarchy2.pop(0)

        del appended
        
        self.status = True

        if filter:
            self.ExpandAll()
        else:
            self.Expand(self.root)
        
            
    def SetFilter(self, filter):

        self.mode.filter = filter.lower()
        self.PopulateTree(self.mode)


    def FindClass(self, fullName):

        self.Freeze()
        self.loop = None
        
        self.LoopItems(fullName)

        if self.loop:        
            self.MakeCurrent(self.loop)

        self.Thaw()
        

    def LoopItems(self, part, item=None):

        split = part.split(".")
        
        if item is None:
            part = ".".join(split[1:])
            self.LoopItems(part, self.root)
            return
        else:
            part = split[0]
            split = ".".join(split[1:])
            
        child, cookie = self.GetFirstChild(item)
        
        while child:

            klass = child.GetData()
            
            if klass.GetShortName() == part:

                parent = self.GetItemParent(child)
                self.Expand(parent)
                self.Expand(child)
                self.loop = child
                    
                self.LoopItems(split, child)

            child, cookie = self.GetNextChild(item, cookie)
    

    def BindEvents(self):

        self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding)
        self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed)
        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelection)
        self.Bind(wx.EVT_TREE_ITEM_GETTOOLTIP, self.OnGetMyToolTip)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
        self.Bind(wx.EVT_MOTION, self.OnMotion)        
        

    def GetMode(self):

        selectedItem = self.GetSelection()
        if selectedItem:
            selection = selectedItem.GetText()
        else:
            selection = None

        mode = self.mode
        
        mode.selectedItem = selection
        mode.expansionState = self.GetExpansionState()
        
        return mode
    

    def GetItemIdentity(self, item):

        data = item.GetData()
        if isinstance(data, list):
            klass, dummy = data
        else:
            klass = data

        if klass is not None:
            return klass.id

        return 0        


    def OnCompareItems(self, item1, item2):

        pydata1, pydata2 = item1.GetData(), item2.GetData()

        if isinstance(pydata1, list):
            order1, order2 = pydata1[0].order, pydata2[0].order
        else:        
            order1, order2 = pydata1.order, pydata2.order
        
        if order1 == order2:
            text1, text2 = item1.GetText(), item2.GetText()
            if "__init__" in text1:
                return -1
            elif "__init__" in text2:
                return 1
            # Same hierarchy: 2 modules, 2 classes etc...
            return cmp(text1.lower(), text2.lower())

        return cmp(order1, order2)


    def OnItemExpanding(self, event):

        item = event.GetItem()

        if self.status or self.mode.filter:
            self.SortChildren(item)
            event.Skip()
            return

        text = item.GetText()
        
        klass = item.GetData()

        if not self.populating:
            wx.BeginBusyCursor()

        self.RecurseChildren(item, klass)
        self.SortChildren(item)

        direct = klass.GetChildrenCount(False)
        total = klass.GetChildrenCount()

        if not self.populating:
            wx.CallAfter(wx.EndBusyCursor)
            
        wx.CallAfter(self.MainFrame.SendMessage, "Message",
                     "Expanded node '%s' has %d direct children (%d total descendants)"%(text, direct, total))


    def OnItemCollapsed(self, event):

        if self.status or self.mode.filter:
            event.Skip()
            return
    
        item = event.GetItem()

        wx.BeginBusyCursor()
        
        self.DeleteChildren(item)
        self.SetItemHasChildren(item, True)

        wx.CallAfter(wx.EndBusyCursor)
        event.Skip()


    def OnSelection(self, event):
        
        item = event.GetItem()
        pyData = item.GetData()

        if pyData is None:
            event.Skip()
            return

        wx.BeginBusyCursor()

        if isinstance(pyData, list):
            klass1, klass2 = pyData
        else:
            klass1, klass2 = pyData, None

        if klass2 is None or klass1 is klass2:
            self.MainFrame.NewSubPage(klass1, None, item)
        else:
            self.MainFrame.NewSubPage(klass1, klass2, item)

        self.SetFocusIgnoringChildren()
        wx.CallAfter(wx.EndBusyCursor)
        

    def OnGetMyToolTip(self, event):
        """
        Handles a the ``wx.EVT_TREE_ITEM_GETTOOLTIP`` events for L{DBTree}.

        :param `event`: a `wx.MouseEvent` event to be processed.
        """

        event.Veto()

        if not self.MainFrame.treeTips:
            return
        
        item = event.GetItem()

        if item == self.tipItem:
            return

        self.HideTip()

        if self.tipWindow is None:
            self.tipWindow = TransientPopup(self, item)

        self.tipWindow.FormatTipWindow(item)
        self.tipItem = item


    def OnLeaveWindow(self, event):

        event.Skip()

        if self.GetScreenRect().Contains(event.GetPosition()):
            return
        
        self.HideTip()


    def OnMotion(self, event):

        event.Skip()

        if not self.GetScreenRect().Contains(event.GetPosition()):
            self.HideTip()
        

    def HideTip(self):
        """ If a tooltip window over a tree item has been created, this method hides it. """

        if self.tipWindow:

            try:
                self.tipWindow.GetTipWindow().Hide()
            except (wx.PyDeadObjectError, AttributeError):
                pass
            
